#include <Arduino.h>

// 2022.6.14——ESP01S 的blinker 知心天气移植 调试完成的最终版
/* ////////////////////////////////////////////////
    实验板：ESP01S
    版本：V0.5
    更改内容:
        V0.1：blinker按键开关调试完成
            1.图标，颜色，文本显示
        V0.2：blinker数字显示num(无会员)调试完成
            1.使用心跳包更新数据
        V0.3：blinker数字显示num(有会员)调试完成
            1.使用RT实时更新数据num数据和图表数据
            2.图表实时和储存数据库可视化显示（注意不管什么实时显示，都有在RT包中添加对应的变量更新）
            3.增加了随机数据的生成，用于测试
        V0.4:文本框作为报警提示 调试完成
            1.发现num不能使用方法改变图标和文本，只能用文本框来代替
        V0.5:知心天气移植(没有关闭调试串口信息打印)
            1.显示在中间的文本框中
            2.json的全解析，针对于这个json，for内的变量，for外就不能用了，只能去掉循环
            3.字符串的增加操作（在天气文本框中实现的）
            4.全篇注释
            5.未解决的：偶尔重启，查询资料说是在loop中定义的变量不会像局部变量一样回收导致内存不足
*/
////////////////////////////////////////////////
#define BLINKER_PRINT Serial
#define BLINKER_WIFI
#define ON 1
#define OFF 2
//!这三个头文件只能放在这里，不能动，动了就报错，注意注意
#include <ArduinoJson.h> //!关键点json必须再blinker之前包含库
#include <Blinker.h>
#include <WiFiManager.h>//用于申请客户端
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
char auth[] = "e8aa3e63d644"; // blinker设备码
char ssid[] = "123456";       // wifi名
char pswd[] = "776654321";    // wifi密码
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//=新建按键对象和对应变量
char b_on;                             //舵机开关
BlinkerButton Button_on("b_on");       //舵机按钮
char b_lsens;                          //光敏开关
BlinkerButton Button_lsens("b_lsens"); //光敏按钮
char b_dth;                            //温湿度开关
BlinkerButton Button_dth("b_dth");     //温湿度按钮
char b_711;                            //称重开关
BlinkerButton Button_711("b_711");     //称重按钮
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//=新建数字显示对象和对应变量
int n_lsens = 10;                      //光敏变量显示
BlinkerNumber Number_lsens("n_lsens"); //光敏变量显示框
int n_tem = 20;                        //温度变量显示
BlinkerNumber Number_tem("n_tem");     //温度变量显示框
int n_hum = 30;                        //湿度变量显示
BlinkerNumber Number_hum("n_hum");     //湿度变量显示框
int n_711 = 0;                         //重量变量显示
BlinkerNumber Number_711("n_711");     //重量变量显示框
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//=文本实例
BlinkerText Text_temp("t_temp");       //温度报警文本
BlinkerText Text_clothes("t_clothes"); //衣服称重报警文本
BlinkerText Text_weather("t_weather"); //天气文本，能显示两行
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//=知心天气请求相关声明
WiFiClient client1;          //实例化客户端
int httpPort = 80;           //请求端口
char weather_times;           //每多少次请求一下天气，需要写自加1操作
char weather_times_limit = 8; //作用：第一次5次之后调用天气接口，第二次30次再调用
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
const char *host = "api.seniverse.com"; // 天气API server
String key = "SeiP2ViJVtkngR1s5";//知心天气的密钥
String location = "luoyang";//请求天气城市
String language = "zh-Hans";//语言
String jsonAnswer_3day;//全局变量3天天气的json格式，是从respon的复杂转换过来的，之后json解析的输入值
String jsonAnswer_today;//全局变量今天天气的json格式，是从respon的复杂转换过来的，之后json解析的输入值
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//=按键回调函数
// 舵机按键开关回调函数
void button_on_callback(const String &state)
{
    // BLINKER_LOG("get button state: ", state); //串口打印get button state:
    if (state == "on")
    {
        digitalWrite(LED_BUILTIN, LOW);
        b_on = ON;
        Button_on.color("#1E90FF");                 //蓝色
        Button_on.icon("fas fa-angle-double-down"); //图标变化
        Button_on.text("衣架已升起");               //文字显示
        Button_on.print("on");                      // 反馈开关状态
    }
    else if (state == "off")
    {
        digitalWrite(LED_BUILTIN, HIGH);
        b_on = OFF;
        Button_on.color("#1E90FF");
        Button_on.icon("fas fa-angle-double-up");
        Button_on.text("衣架已降下");
        Button_on.print("off"); // 反馈开关状态
    }
}
// 光敏按键开关回调函数
void button_lsens_callback(const String &state)
{
    // BLINKER_LOG("get button state: ", state); //串口打印get button state:
    if (state == "on")
    {
        digitalWrite(LED_BUILTIN, LOW);
        b_lsens = ON;
        Button_lsens.color("#1E90FF");         //蓝色
        Button_lsens.icon("fas fa-lightbulb"); //图标变化
        Button_lsens.text("光敏模块开启");     //文字显示
        Button_lsens.print("on");              // 反馈开关状态
    }
    else if (state == "off")
    {
        digitalWrite(LED_BUILTIN, HIGH);
        b_lsens = OFF;
        Button_lsens.color("#1E90FF");
        Button_lsens.icon("far fa-lightbulb-slash");
        Button_lsens.text("光敏模块关闭");
        Button_lsens.print("off"); // 反馈开关状态
    }
}
//温湿度按键开关回调函数
void button_dth_callback(const String &state)
{
    // BLINKER_LOG("get button state: ", state); //串口打印get button state:
    if (state == "on")
    {
        digitalWrite(LED_BUILTIN, LOW);
        b_dth = ON;
        Button_dth.color("#1E90FF");       //蓝色
        Button_dth.icon("fas fa-tint");    //图标变化
        Button_dth.text("温湿度检测开启"); //文字显示
        Button_dth.print("on");            // 反馈开关状态
    }
    else if (state == "off")
    {
        digitalWrite(LED_BUILTIN, HIGH);
        b_dth = OFF;
        Button_dth.color("#1E90FF");
        Button_dth.icon("fas fa-tint-slash");
        Button_dth.text("温湿度检测关闭");
        Button_dth.print("off"); // 反馈开关状态
    }
}
// 称重按键开关回调函数
void button_711_callback(const String &state)
{
    // BLINKER_LOG("get button state: ", state); //串口打印get button state:
    if (state == "on")
    {
        digitalWrite(LED_BUILTIN, LOW);
        b_711 = ON;
        Button_711.color("#1E90FF");                  //蓝色
        Button_711.icon("fas fa-hand-holding-water"); //图标变化
        Button_711.text("干湿检测开启");              //文字显示
        Button_711.print("on");                       // 反馈开关状态
    }
    else if (state == "off")
    {
        digitalWrite(LED_BUILTIN, HIGH);
        b_711 = OFF;
        Button_711.color("#1E90FF");
        Button_711.icon("fas fa-hand-holding-water");
        Button_711.text("干湿检测关闭");
        Button_711.print("off"); // 反馈开关状态
    }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//=实时数据更新回调和数据储存回调
// 如果未绑定的组件被触发，则会执行其中内容
void dataRead(const String &data)
{
    // BLINKER_LOG("Blinker readString: ", data);
    // counter++;
    // Number1.print(counter);
}
//实时数据包，用于发送数据同步（每1s同步一次数据，注意函数的变化）
void RTdata()
{
    Blinker.sendRtData("n_lsens", n_lsens); //使用实时数据功能，可以将数据更新频率提升至每秒一次
    Blinker.sendRtData("n_tem", n_tem);
    Blinker.sendRtData("n_hum", n_hum);
    Blinker.sendRtData("n_711", n_711);
    //图表的实时显示数据，不添加这个，图表的实时显示没有，只会显示最近一小时的数据（储存数据）
    Blinker.sendRtData("n_tem", n_tem);
    Blinker.sendRtData("n_hum", n_hum);
    Blinker.sendRtData("n_711", n_711);
    //

    // Number_lsens.print(n_lsens);         //每半分钟刷新一次
    // Number_tem.print(n_tem);
    // Number_hum.print(n_hum);
    // Number_711.print(n_711);
    // Number_remote.print(n_remote);
    Blinker.printRtData(); // Blinker.printRtData调用后，才可正常发送数据。
}
//开启图标数据储存，键值对，用于最近一小时和之后的数据存储
void dataStorage()
{
    Blinker.dataStorage("n_tem", n_tem);
    Blinker.dataStorage("n_hum", n_hum);
    Blinker.dataStorage("n_711", n_711);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//=心知天气的3天预报
void client_respon_3day()
{
    String answer;
    int jsonIndex;
    if (!client.connect(host, httpPort)) // TCP连接是否成功，不成功再重试
    {
        Serial.println("connection failed");
        return;
    }

    //!请求网址
    String url = "/v3/weather/daily.json?key=" + key + "&location=" + location + "&language=" + language + "&unit=c&start=0&days=3";
    client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + host + "\r\n" +
                 "Connection: close\r\n\r\n");
    Blinker.delay(100);
    //将每行的返回值加入到answer
    while (client.available())
    {
        String line = client.readStringUntil('\r'); // terminator: 终止字符。用于设置终止函数执行的字符信息。设备在读取数据时一旦读取到此终止字符，将会结束函数执行。允许使用char类型。
        answer += line;
    }
    client.stop(); //关闭连接
    //!以下为将string数据answer处理为json
    for (int i = 0; i < answer.length(); i++) //如果开头为{目录就为i
    {
        if (answer[i] == '{')
        {
            jsonIndex = i;
            break;
        }
    }
    jsonAnswer_3day = answer.substring(jsonIndex);
}
//心知天气的今天实时预报
void client_respon_today()
{
    String answer;
    int jsonIndex;
    if (!client.connect(host, httpPort)) // TCP连接是否成功，不成功再重试
    {
        Serial.println("connection failed");
        return;
    }

    //!请求网址
    String url = "/v3/weather/now.json?key=" + key + "&location=" + location + "&language=" + language + "&unit=c";
    client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + host + "\r\n" +
                 "Connection: close\r\n\r\n");
    Blinker.delay(100);
    //将每行的返回值加入到answer
    while (client.available())
    {
        String line = client.readStringUntil('\r'); // terminator: 终止字符。用于设置终止函数执行的字符信息。设备在读取数据时一旦读取到此终止字符，将会结束函数执行。允许使用char类型。
        answer += line;
    }
    client.stop(); //关闭连接
    //!以下为将string数据answer处理为json
    for (int i = 0; i < answer.length(); i++) //如果开头为{目录就为i
    {
        if (answer[i] == '{')
        {
            jsonIndex = i;
            break;
        }
    }
    jsonAnswer_today = answer.substring(jsonIndex);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//=初始化函数
void setup()
{
    //-1.初始化串口
    Serial.begin(115200);
#if defined(BLINKER_PRINT)
    BLINKER_DEBUG.stream(BLINKER_PRINT); //用于指定调试信息输出的串口，设备开发时调试使用
#endif
    //-2.初始化有LED的IO
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);
    //-3.初始化blinker
    Blinker.begin(auth, ssid, pswd);
    Blinker.attachData(dataRead);
    //-4.按键开关绑定
    Button_on.attach(button_on_callback);
    Button_lsens.attach(button_lsens_callback);
    Button_dth.attach(button_dth_callback);
    Button_711.attach(button_711_callback);
    //-5.初始化数据储存和实时数据
    Blinker.attachRTData(RTdata);
    Blinker.attachDataStorage(dataStorage);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//=循环函数
void loop()
{
    Blinker.run();//blinker客户端运行

    n_lsens = random(300);//几个变量的随机数
    n_tem = random(35);
    n_hum = random(300);
    n_711 = random(300);

    //两个文本报警的数据判断操作
    if (n_tem >= 30)
    {
        Text_temp.color("#DC143C"); //红色
        Text_temp.print("高温预警", "立刻检查");
    }
    else
    {
        Text_temp.color("#6495ED");
        Text_temp.print("温度正常", "无需操作");
    }
    if (n_hum >= 200)
    {
        Text_clothes.color("#0000FF"); //红色
        Text_clothes.print("衣服已干", "1小时前");
    }
    else
    {
        Text_clothes.color("#008000");
        Text_clothes.print("衣服未干", "无需操作");
    }

    //!最复杂的天气请求，编辑响应的格式，转换为json格式，再调用json格式
    if (weather_times >= weather_times_limit)
    {
        weather_times = 0;//循环变量
        weather_times_limit = 15;//循环次数限制变量
        client_respon_3day();//请求3天的
        client_respon_today();//请求今天的

        // String input;
        DynamicJsonDocument doc(1536);
        DeserializationError error = deserializeJson(doc, jsonAnswer_3day);
        JsonObject results_0 = doc["results"][0];

        JsonObject results_0_location = results_0["location"];
        const char *results_0_location_name = results_0_location["name"];         // "洛阳"
        const char *results_0_location_path = results_0_location["path"];         // "洛阳,洛阳,河南,中国"
        const char *results_0_location_timezone = results_0_location["timezone"]; // "Asia/Shanghai"

        //!关键点，把daily下的每一个{}视为数组调用，现在调用的是今天的数据
        JsonObject results_0_daily_item0 = results_0["daily"][0];
        const char *results_0_daily_item0_date = results_0_daily_item0["date"];                                   //日期
        const char *results_0_daily_item0_text_day = results_0_daily_item0["text_day"];                           //白天天气现象文字
        const char *results_0_daily_item0_text_night = results_0_daily_item0["text_night"];                       //晚间天气现象文字
        const char *results_0_daily_item0_high = results_0_daily_item0["high"];                                   //当天最高温度
        const char *results_0_daily_item0_low = results_0_daily_item0["low"];                                     //当天最低温度
        const char *results_0_daily_item0_rainfall = results_0_daily_item0["rainfall"];                           //降水量，单位mm
        const char *results_0_daily_item0_precip = results_0_daily_item0["precip"];                               //降水概率，范围0~100，单位百分比
        const char *results_0_daily_item0_wind_direction = results_0_daily_item0["wind_direction"];               //风向文字
        const char *results_0_daily_item0_wind_direction_degree = results_0_daily_item0["wind_direction_degree"]; //风向角度，范围0~360
        const char *results_0_daily_item0_wind_speed = results_0_daily_item0["wind_speed"];                       //风速，单位km/h
        const char *results_0_daily_item0_wind_scale = results_0_daily_item0["wind_scale"];                       //风力等级
        const char *results_0_daily_item0_humidity = results_0_daily_item0["humidity"];                           //相对湿度，0~100，单位为百分比
        //!关键点，把daily下的每一个{}视为数组调用，现在调用的是明天的数据
        JsonObject results_0_daily_item1 = results_0["daily"][1];
        const char *results_0_daily_item1_date = results_0_daily_item1["date"];                                   //日期
        const char *results_0_daily_item1_text_day = results_0_daily_item1["text_day"];                           //白天天气现象文字
        const char *results_0_daily_item1_text_night = results_0_daily_item1["text_night"];                       //晚间天气现象文字
        const char *results_0_daily_item1_high = results_0_daily_item1["high"];                                   //当天最高温度
        const char *results_0_daily_item1_low = results_0_daily_item1["low"];                                     //当天最低温度
        const char *results_0_daily_item1_rainfall = results_0_daily_item1["rainfall"];                           //降水量，单位mm
        const char *results_0_daily_item1_precip = results_0_daily_item1["precip"];                               //降水概率，范围0~100，单位百分比
        const char *results_0_daily_item1_wind_direction = results_0_daily_item1["wind_direction"];               //风向文字
        const char *results_0_daily_item1_wind_direction_degree = results_0_daily_item1["wind_direction_degree"]; //风向角度，范围0~360
        const char *results_0_daily_item1_wind_speed = results_0_daily_item1["wind_speed"];                       //风速，单位km/h
        const char *results_0_daily_item1_wind_scale = results_0_daily_item1["wind_scale"];                       //风力等级
        const char *results_0_daily_item1_humidity = results_0_daily_item1["humidity"];                           //相对湿度，0~100，单位为百分比
        //!关键点，把daily下的每一个{}视为数组调用，现在调用的是后天的数据
        JsonObject results_0_daily_item2 = results_0["daily"][2];
        const char *results_0_daily_item2_date = results_0_daily_item2["date"];                                   //日期
        const char *results_0_daily_item2_text_day = results_0_daily_item2["text_day"];                           //白天天气现象文字
        const char *results_0_daily_item2_text_night = results_0_daily_item2["text_night"];                       //晚间天气现象文字
        const char *results_0_daily_item2_high = results_0_daily_item2["high"];                                   //当天最高温度
        const char *results_0_daily_item2_low = results_0_daily_item2["low"];                                     //当天最低温度
        const char *results_0_daily_item2_rainfall = results_0_daily_item2["rainfall"];                           //降水量，单位mm
        const char *results_0_daily_item2_precip = results_0_daily_item2["precip"];                               //降水概率，范围0~100，单位百分比
        const char *results_0_daily_item2_wind_direction = results_0_daily_item2["wind_direction"];               //风向文字
        const char *results_0_daily_item2_wind_direction_degree = results_0_daily_item2["wind_direction_degree"]; //风向角度，范围0~360
        const char *results_0_daily_item2_wind_speed = results_0_daily_item2["wind_speed"];                       //风速，单位km/h
        const char *results_0_daily_item2_wind_scale = results_0_daily_item2["wind_scale"];                       //风力等级
        const char *results_0_daily_item2_humidity = results_0_daily_item2["humidity"];                           //相对湿度，0~100，单位为百分比

        const char *results_0_last_update = results_0["last_update"]; // "2022-06-14T08:00:00+08:00"

        //调试接口，功能调试完成后就注释掉了
        // Serial.print(results_0_daily_item0_date);   //今天日期
        // Serial.print(results_0_daily_item1_date);   //明天日期
        // Serial.println(results_0_daily_item2_date); //后天日期

        // Serial.print(results_0_daily_item0_text_day);   //今天天气
        // Serial.print(results_0_daily_item1_text_day);   //明天天气
        // Serial.println(results_0_daily_item2_text_day); //后天天气

        // String input;
        StaticJsonDocument<512> doc1;
        DeserializationError error1 = deserializeJson(doc1, jsonAnswer_today);
        JsonObject results = doc1["results"][0];

        JsonObject results_location = results["location"];
        const char *results_location_name = results_location["name"]; // "洛阳"
        const char *results_location_path = results_location["path"]; // "洛阳,洛阳,河南,中国"
        //!关键点，模仿location的调用方法
        JsonObject results_now = results["now"];
        const char *results_now_text = results_now["text"];               // "晴"
        const char *results_now_temperature = results_now["temperature"]; // "30"
        const char *results_last_update = results["last_update"];         // "2022-06-14T20:20:09+08:00"

        //调试接口，功能调试完成后就注释掉了
        // Serial.println(jsonAnswer_today);
        // Serial.println(results_location_path);   //具体地区
        // Serial.println(results_now_text);        //实时获取天气
        // Serial.println(results_now_temperature); //实时获取温度
        // Serial.println(results_last_update);     //显示更新时间
        //!字符串的增加方法
        //文本1：今日洛阳天气： 晴  33℃
        String weather1 = String("今日") + String(results_location_name) + String("天气") + String(": ") + String(results_now_text) + String("   ") + String(results_now_temperature) + String("℃");
        //文本2：明日天气：多云  最高气温：38℃   最低气温：23℃
        String weather2 = String("明日天气") + String(": ") + String(results_0_daily_item1_text_day) + String("   ") + String("最高气温:") + String(results_0_daily_item1_high) + String("℃") + String("   ") + String("最低气温:") + String(results_0_daily_item1_low) + String("℃");
        Text_weather.print(weather1, weather2);
        Blinker.delay(1000); //!只能使用这个延时，delay会导致设备离线，Blinker.delay能在延时中保证RT数据的实时发送且不掉线
    }
    weather_times++; //!声明=1写loop里导致一直是2，脑残了，还忘了weather_times++
    Serial.print(weather_times);
    Blinker.delay(3000); //!只能使用这个延时，delay会导致设备离线，Blinker.delay能在延时中保证RT数据的实时发送

}
